package com.aquarium.bt;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;

import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.util.Log;

import com.aquarium.config.AlarmDefaultValue;
import com.aquarium.config.BTDataConfig;

public class BTManager {
	private final static String TAG = "BTManager";
	private final static UUID COMMOM_UUID = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");

	private static BTManager mBTManager;
	private BluetoothAdapter mBluetoothAdapter;
	private BluetoothDevice mBluetoothDevice;
	private BluetoothSocket mBluetoothSocket;
	private static RecvDataDispatcher mRecvDataDispatcher;
	private StatusChangedListener mStatusChangedListener;
	private InputStream mInputStream;
	private OutputStream mOutputStream;
	private DataInterpreter mDataInterpreter;
	private Thread mConnectThread;
	private Thread mRecvDataThread;
	private Thread mSendDataThread;

	private ArrayBlockingQueue<DataStruct> mSendDataQueue;
	private Handler mRecvHandler;
	private Boolean mIsInit;
	private Boolean mFlag;
	private int mStatus;
	// test code
	private Timer mSendSensorValueTimer;
	private Context mContext;

	public interface StatusChangedListener {
		void onChanged(int status);
	}

	private BTManager() {
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		mSendDataQueue = new ArrayBlockingQueue<DataStruct>(100);
		mDataInterpreter = new DataInterpreter();
		mStatus = Status.OFF;
		mIsInit = false;
		mFlag = false;
	}

	public class Status {
		public static final int OFF = 0;
		public static final int TURNING_ON = 1;
		public static final int ON = 2;
		public static final int DISCOVERYING = 3;
		public static final int CONNECTING = 4;
		public static final int CONNECTED = 5;
	}

	public static BTManager getInstance() {
		if (mBTManager == null) {
			mBTManager = new BTManager();
		}
		return mBTManager;
	}

	/**
	 * 负责连接到remote BT device
	 */
	public void connect(BluetoothDevice device) {
		if (!mIsInit) {
			Log.e(TAG, "connect(), do init() fitst!");
			return;
		}
		if (!isOpen()) {
			Log.i(TAG, "bluetooth does not open!");
			return;
		}
		if (mStatus == Status.CONNECTING || mStatus == Status.CONNECTED) {
			Log.i(TAG, "bluetooth is connecting or has been connected!");
			return;
		}
		try {
			mBluetoothSocket = device
					.createRfcommSocketToServiceRecord(COMMOM_UUID);
		} catch (IOException e) {
			Log.e(TAG, "create bluetooth socket failed!");
			e.printStackTrace();
		}
		mConnectThread = new Thread(new Runnable() {

			@Override
			public void run() {
				mBluetoothAdapter.cancelDiscovery();
				try {
					setStatus(Status.CONNECTING);
					Log.i(TAG, "start to connect!");
					mBluetoothSocket.connect();
				} catch (IOException e) {
					Log.e(TAG, "connet to remote bluetooth device failed!");
					e.printStackTrace();
					setStatus(Status.ON);
					try {
						mBluetoothSocket.close();
					} catch (IOException e2) {
						Log.e(TAG,
								"unable to close() socket during connection failure",
								e2);
						return;
					}
					return;
				}
				try {
					mInputStream = mBluetoothSocket.getInputStream();
					mOutputStream = mBluetoothSocket.getOutputStream();
				} catch (IOException e) {
					Log.e(TAG, "get data stream error!");
					e.printStackTrace();
					setStatus(Status.ON);
				}
				setStatus(Status.CONNECTED);
				mFlag = true;
				startRecvDataThread();
				startSendDataThread();
			}
		});
		mConnectThread.start();
	}

	public void init(Context c) {
		mContext = c;
		IntentFilter filter = new IntentFilter(
				BluetoothAdapter.ACTION_STATE_CHANGED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
		c.registerReceiver(mReceiver, filter);
		mIsInit = true;

		if (isOpen()) {
			mStatus = Status.ON;
		}
	}

	public void open() {
		if (!mIsInit) {
			Log.e(TAG, "open(), do init() fitst!");
			return;
		}
		if (mStatus == Status.OFF) {
			mBluetoothAdapter.enable();
		}
	}

	public void close() {
		mFlag = false;
		mBluetoothAdapter.disable();
		mStatus = Status.OFF;
		mContext.unregisterReceiver(mReceiver);
		try {
			mBluetoothSocket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public boolean isOpen() {
		if (!mIsInit) {
			Log.e(TAG, "isOpen(), do init() fitst!");
			return false;
		}
		return mBluetoothAdapter.isEnabled();
	}

	public void sendData(DataStruct data) {
		if (!mIsInit) {
			Log.e(TAG, "sendData(), do init() fitst!");
			return;
		}
		try {
			mSendDataQueue.put(data);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Log.i(TAG, "put data to SendDataQueue failed!");
		}
	}

	// test code
	public void sendData(String data) {
		if (!mIsInit) {
			Log.e(TAG, "sendData(), do init() fitst!");
			return;
		}
		try {
			DataStruct s = new DataStruct();
			s.data = data;
			mSendDataQueue.put(s);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Log.i(TAG, "put data to SendDataQueue failed!");
		}
	}

	public synchronized void startDiscovery() {
		if (!mIsInit) {
			Log.e(TAG, "startDiscovery(), do init() fitst!");
			return;
		}
		new Thread(new Runnable() {

			@Override
			public void run() {
				long t = System.currentTimeMillis();
				while (mStatus == Status.OFF || mStatus == Status.TURNING_ON) {
					if (System.currentTimeMillis() - t > 5000) {
						Log.e(TAG, "BT turn on fail!");
						return;
					}
				}
				if (mStatus != Status.ON) {
					return;
				}
				Log.i(TAG, "start discovery!");
				boolean result = mBluetoothAdapter.startDiscovery();
				if (!result) {
					Log.e(TAG, "unable to find remote bluetooth device!");
				}
			}
		}).start();

	}

	private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Log.i(TAG, "action found!! " + action);
			if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
				int status = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
						-1);
				if (status == BluetoothAdapter.STATE_ON) {
					mStatus = Status.ON;
				} else if (status == BluetoothAdapter.STATE_OFF) {
					mStatus = Status.OFF;
				} else if (status == BluetoothAdapter.STATE_TURNING_ON) {
					mStatus = Status.TURNING_ON;
				}
			}
			if (BluetoothDevice.ACTION_FOUND.equals(action)) {
				BluetoothDevice device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				// test code
				Log.i(TAG, "start discovery??" + device.getName());

				if (device.getName().equals("BTCOM")) {
					if (device.getBondState() == BluetoothDevice.BOND_BONDED) {

						connect(device);
					} else {
						try {
							// setPin(device.getClass(), device, "0000");
							createBond(device.getClass(), device);
							// cancelPairingUserInput(device.getClass(),
							// device);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
				BluetoothDevice device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				Log.i(TAG, "bound state = " + device.getBondState());
				if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
					}
					connect(device);
				} else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
					try {
						Thread.sleep(1000);
						// setPin(device.getClass(), device, "0000");
						createBond(device.getClass(), device);
						// cancelPairingUserInput(device.getClass(),
						// device);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}

			} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
					.equals(action)) {
				// setProgressBarIndeterminateVisibility(false);
				// setTitle(R.string.select_device);
				// if (mNewDevicesArrayAdapter.getCount() == 0) {
				// String noDevices = getResources().getText(
				// R.string.none_found).toString();
				// mNewDevicesArrayAdapter.add(noDevices);
				// }
			}
		}
	};

	/**
	 * 发送数据或命令
	 */
	private void startSendDataThread() {
		if (mStatus != Status.CONNECTED) {
			Log.e(TAG, "does not connect to any remote device!");
			return;
		}
		mSendDataThread = new Thread(new Runnable() {
			DataStruct data;
			byte[] bytes;

			@Override
			public void run() {
				while (mFlag) {
					try {
						data = mSendDataQueue.take();
						Log.i(TAG, "send data..." + data.data);
					} catch (InterruptedException e) {
						e.printStackTrace();
						Log.i(TAG, "take data failed!");
					}
					bytes = mDataInterpreter.pack(data);
					try {
						mOutputStream.write(data.data.getBytes());
					} catch (IOException e) {
						e.printStackTrace();
						Log.e(TAG, "send data error!");
						continue;
					}
				}
			}
		});
		mSendDataThread.start();
	}

	private void startRecvDataThread() {
		if (mStatus != Status.CONNECTED) {
			Log.e(TAG, "does not connect to any remote device!");
			return;
		}
		mRecvDataThread = new Thread(new Runnable() {

			@Override
			public void run() {
				byte[] buffer = new byte[1024];
				int bytes;

				while (mFlag) {
					try {
						bytes = mInputStream.read(buffer);
						if (mRecvHandler != null) {
							mRecvHandler.obtainMessage(
									RecvDataDispatcher.HANDLE_RECV_DATA, bytes,
									-1, buffer).sendToTarget();
						}

					} catch (IOException e) {
						Log.e(TAG, "read data error!", e);
						continue;
					}
				}
			}
		});
		mRecvDataThread.start();

	}

	private boolean createBond(Class btClass, BluetoothDevice btDevice)
			throws Exception {
		Method createBondMethod = btClass.getMethod("createBond");
		Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
		return returnValue.booleanValue();
	}

	private boolean setPin(Class btClass, BluetoothDevice btDevice, String str)
			throws Exception {
		try {
			Method removeBondMethod = btClass.getDeclaredMethod("setPin",
					new Class[] { byte[].class });
			Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,
					new Object[] { str.getBytes() });
			Log.e("returnValue", "" + returnValue);
		} catch (SecurityException e) {
			// throw new RuntimeException(e.getMessage());
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// throw new RuntimeException(e.getMessage());
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;

	}

	private boolean cancelPairingUserInput(Class btClass, BluetoothDevice device)
			throws Exception {
		Method createBondMethod = btClass.getMethod("cancelPairingUserInput");
		// cancelBondProcess()
		Boolean returnValue = (Boolean) createBondMethod.invoke(device);
		return returnValue.booleanValue();
	}

	public void setRecvHandler(Handler handler) {
		if (!mIsInit) {
			Log.e(TAG, "setRecvHandler(), do init() fitst!");
			return;
		}
		mRecvHandler = handler;
	}

	private void setStatus(int status) {
		mStatus = status;
		if (mStatusChangedListener != null) {
			mStatusChangedListener.onChanged(mStatus);
		}
	}

	public void setStatusChangedListener(StatusChangedListener l) {
		if (!mIsInit) {
			Log.e(TAG, "setStatusChangedListener(), do init() fitst!");
			return;
		}
		mStatusChangedListener = l;
	}

	/**
	 * test code
	 */
	public void sendSensorValueTest() {
		if (mSendSensorValueTimer != null) {
			mSendSensorValueTimer.cancel();
		}
		mSendSensorValueTimer = new Timer();
		mSendSensorValueTimer.schedule(new TimerTask() {

			@Override
			public void run() {
				DataStruct struct = new DataStruct();
				struct.code = BTDataConfig.RECV_HP_VALUE;
				struct.data = String.valueOf(getRandamFloat(
						AlarmDefaultValue.MINPH - 0.3f,
						AlarmDefaultValue.MAXPH + 0.3f, 1));
				RecvDataDispatcher.getInstance().recv(struct);
				struct.code = BTDataConfig.RECV_TEMPERATURE_VALUE;
				struct.data = String.valueOf(getRandamFloat(
						AlarmDefaultValue.MINTEMPERATURE - 10f,
						AlarmDefaultValue.MAXTEMPERATURE + 10f, 0));
				RecvDataDispatcher.getInstance().recv(struct);
				struct.code = BTDataConfig.RECV_OXYGEN_VALUE;
				struct.data = String.valueOf(getRandamFloat(
						AlarmDefaultValue.MINOxygen - 10f,
						AlarmDefaultValue.MAXOxygen + 10f, 0));
				RecvDataDispatcher.getInstance().recv(struct);
				struct.code = BTDataConfig.RECV_CONDUCTIVITY_VALUE;
				struct.data = String.valueOf(getRandamFloat(
						AlarmDefaultValue.MINConductivity - 0.005f,
						AlarmDefaultValue.MAXConductivity + 0.005f, 3));
				RecvDataDispatcher.getInstance().recv(struct);
				struct.code = BTDataConfig.RECV_WATER_LEVEL_VALUE;
				struct.data = String.valueOf(getRandamFloat(
						AlarmDefaultValue.MINWATERLEVEL - 10f,
						AlarmDefaultValue.MAXWATERLEVEL + 10f, 0));
				RecvDataDispatcher.getInstance().recv(struct);
			}
		}, 0, 5000);

	}

	/**
	 * test code
	 */
	public void stopSendSensorValueTest() {
		if (mSendSensorValueTimer == null) {
			return;
		}
		mSendSensorValueTimer.cancel();
	}

	/**
	 * test code
	 * 
	 * @param min
	 * @param max
	 * @return
	 */
	private float getRandamFloat(float min, float max, int accurancy) {
		float value = (float) (Math.random() * (max - min) + min);
		value = (float) ((int) (value * Math.pow(10, accurancy)) / Math.pow(10,
				accurancy));
		return value;
	}
}
